module AI


class Status #current action
  Idle=0 #action = job
  Engaged=1 #action = attack
  Fleeing=2 #action = flee
  Subdued=3 #unable to act
  InSquad=4 #let squad leader handle AI
  Engaging=5 #enroute to combat
  FallingBack=6 
  JoiningSquad=7
  Waiting=8 #last order finished, awaiting new one
end

#A character's Alignment indicates his 'team'. Characters will cooperate with other characters of the same Alignment and will attack characters of different Alignments.
class Alignment
  Good=0     #Aligned with player
  Neutral=1  #Non combatant - will not attack
  Evil=2       #Aligned against player
  AlignEx1=3 #Extra alignments
  AlignEx2=4
  AlignEx3=5
end
=begin
#A character's Courage is a measure of how likely he is to flee combat, and under what circumstances.
class Courage
  NonCombatant = 0 #Will flee when enemy is spotted
  None=0 #Will flee when enemy is spotted
  Low=1 #Will flee if outnumbered or loosing battle
  Medium=2 #Will flee if taking high damage
  High=3 #Will not flee
  Custom=-1
  ProcMap = {
    None => NoneProc
    Low => LowProc
    Medium => MedProc
    High => HighProc
  }
  NoneProc = {}
  LowProc = {}
  MedProc = {}
  HighProc = {}
end

#A character's Agressiveness determines if and/or when he will engage a nearby enemy.
class Agressiveness
  None=0 #Will avoid fights at all costs. (Will fall back, but not flee)
  Low=1 #Will avoid fights when possible - defensive
  Medium=2 #Will engage when enemy is close/a threat
  High=3 #Will engage enemy whenever possible
  Custom=-1
  ProcMap = {
    None => NoneProc
    Low => LowProc
    Medium => MedProc
    High => HighProc
  }
  NoneProc = {}
  LowProc = {}
  MedProc = {}
  HighProc = {}
end

#A character's Discipline indicates how likely he is to follow orders. A character of low Disciline may ignore orders when he deems it appropriate.
class Discipline
  None=0 #Will assign own job and ignore all other orders
  Low=1 #Will occasionally ignore orders if deemed necessary
  Medium=2 #Will ignore orders on rare occasions
  High=3 #Will never ignore orders
  ProcMap = {
    None => NoneProc
    Low => LowProc
    Medium => MedProc
    High => HighProc
  }
  NoneProc = {}
  LowProc = {}
  MedProc = {}
  HighProc = {}
end

#A character's GroupSkills are indicative of his squad forming and teamwork ability.
class GroupSkills
  None=0 #Will not work in groups
  Low=1 #Can form loose squads
  Medium=2 #Can form organized squads
  High=3 #Can organize into army + nominate general
  ProcMap = {
    None => NoneProc
    Low => LowProc
    Medium => MedProc
    High => HighProc
  }
  NoneProc = {}
  LowProc = {}
  MedProc = {}
  HighProc = {}
end
=end
 
class AISettings #used by character class
  attr_accessor :team, :sight, :status
  attr_accessor :combatai, :job, :engageai, :disengageai, :squad
  def initialize(combatai,job,engageai,disengageai,alignment,sight)
    @combatai=combatai #agressiveness
    @job=job #what character does when idle
    @engageai=engageai #combat style
    @disengageai=disengageai #combat style
    @team=alignment #team - all nonteam members are considered hostile, except for civilian
    @sight=sight #integer - range at which enemies are detected
    @status=Status::Idle
    @prevstatus=Status::Idle
    @squad=nil
  end
end

class Squad
  LOOSE_SQUAD_MEMBERS_DIST_LIM = 150 #Max distance between potential squad members
  LOOSE_SQUAD_TARGETS_DIST_LIM = 75 #Max distance between targets of potential squad members
  LOOSE_SQUAD_MEMBERS_THREAT_LIM = 50 #Min distance between potential threat and potential squad member
  LOOSE_SQUAD_BREAK_DIST = 50 #Distance from target at which squad will break
  LOOSE_SQUAD_MEMBERS_LIM = 10 #Max no of squad members
  LooseSquadMoveProc = Proc.new {} 
  EXP_SQUAD_MEMBERS_DIST_LIM = 80
  EXP_SQUAD_MEMBERS_THREAT_LIM = 40
  EXP_SQUAD_BREAK_DIST = 10
  EXP_SQUAD_MEMBERS_LIM = 6
  ExpSquadMoveProc = Proc.new {}
end

class AI
  MoveToProc = Proc.new { |this| this.ai.finishStep }
  @@squads={}
  attr_reader :claims, :settings
  def initialize(charid, combatai,job,engageai,disengageai,alignment,sight)
    @settings=AISettings.new(combatai,job,engageai,disengageai,alignment,sight)
    include combatai
    include job
    include engageai
    include disengageai
    @claimed=false
    @curvel=Velocity::Walking
    @char = *$window.characters[charid]
  end
  def claim
    @claimed=true
    @claims+=1
  end
  def unclaim
    @claims-=1
    @claimed=false if @claims==0
  end
  def claimed?
    return @claimed
  end
  def inSquad?
    return (@setting.status==Status::InSquad)
  end
  def startAI
    startJob
  end
  def do
    case @settings.status
      when Status::Idle
        @settings.engageai.search
        @settings.jobai.doJob 
      when Status::Engaged
        if @settings.combatai.fallBack?
          @settings.combatai.fallBack
        else
          @settings.combatai.doCombat
        end
      when Status::Engaging
        
      when Status::Fleeing
        #if @char.targetx == nil
        #  target=@char.target
        #  @char.targetx = @char.x + (@char.x - @target.x)
        #  @char.targety = @char.y + (@char.y - @target.y)
        #  @char.movePath(@char.targetx, @char.targety)
        #end
        # if (@char.targetx==@char.x and @char.targety==@char.y)
        
      when Status::FallingBack
        
      when Status::Subdued
        #do nothing
      when Status::InSquad
        #do nothing
      end
  end
  def search
    best = nil
    $window.characters.each { |char|
      if char.id!=@char.id and char.ai.settings.team!=@settings.team and Gosu::distance(@char.x, @char.y, char.x, char.y) < @settings.sight
        if (Gosu::angle(@char.x, @char.y, char.x, char.y) - Gosu::angle(@char.x, @char.y, -char.x, -char.y)).abs < 80
          if best == nil
            best = char
          elsif Gosu::distance(@char.x, @char.y, char.x, char.y) < Gosu::distance(@char.x, @char.y, best.x, best.y)
            best = char
          end
        end
      end
    }
    if best!=nil
      @char.setTarget(best) 
      best.ai.claim
    end
  end
  def makeSquad(tchar)
    return unless char.ai.settings.squad == nil
    squad = char.ai.settings.team * 100 + char.id
    char.ai.settings.squad = squad
    chars=[]
    $window.characters.each { |char|
      if char.id!=@char.id and char.ai.settings.team==@settings.team and Gosu::distance(@char.x, @char.y, char.x, char.y) < SQUAD_MEMBERS_DIST_LIM
        if char.target==nil
          char.ai.search
        end
        if Gosu::distance(@char.target.x, @char.target.y, char.target.x, char.target.x) < SQUAD_TARGETS_DIST_LIM
          chars.push(char.id)
        end
      end
    }
    AI.newSquad(squad, chars)
    chars.each { |char|
      char.ai.moveTo(@char.target.x, @char.target.y)#,...)
    }
  end
  def AI.newSquad(id, chars)
    @@squad[id] = chars
  end
  def AI.getSquad(id)
    return @@squad[id]
  end
  def AI.delSquad(id)
    @@squad[id].each { |char|
      $window.characters[char].ai.settings.squad=nil
    }
    @@squad[id]=nil
  end
end

module CombatAI
  def engage(enemychar); return false; end
  def doCombat(enemychar); end 
  def fallBack(enemychar); end
  def fallBack?(enemychar); return false; end
  def stopFallBack?(enemychar); return false; end
end

module JobAI
  def startJob; end
  def doJob; end
  def endJob; end
end

module EngageAI
  def search; return false; end
  def engage?(enemychar); return false; end
  def claimEnemy; end
end

module DisengageAI
  def flee?(enemychar); return false; end
  def flee(enemychar); end
  def stopFlee?; end
end

module SquadAI
  def SquadAI.formSquad?(team); return false; end
  def SquadAI.chooseSquadLeader(team); end
  def SquadAI.formSquad(leaderchar); end
  def joinSquad?(enemychar); return false; end
  def joinSquad; end
end

def AIProc(char,settings=nil)
end

module AStar

class Node
  attr_accessor :x,:y
  attr_reader :pg
  def initialize(x, y, px=nil, py=nil, pg=nil)
    @x=x
    @y=y
    @parent=[px,py]
    @pg=pg
    @parentg=nil
    @g=nil
    @h=nil
  end
  def g(pg=nil)
    if @g==nil
      @g=getG(@x, @y, @parent[0], @parent[1], pg)
      @parentg=pg
    end
    return @g
  end
  def hypg(px, py, pg=@parentg)
    return getG(@x, @y, px, py, pg)
  end
  def h(cx=@parent[0], cy=@parent[1])
    if @h==nil
      @h=ManhattanH(@x, @y, cx, cy)
    end
    return @h
  end
  def f
    return g+h
  end
  def parent
    return @parent
  end
  def parent=(px, py, pg=nil)
    @parent[0],@parent[1],@parentg=px,py,pg
  end
  def parentg
    return @parentg
  end
  def ==(obj)
  #  if obj.kind_of?(Point)
      return (@x==obj.x and @y==obj.y)
  #  elsif obj.kind_of?(Array)
  #    return (@x==obj[0] and @y==obj[1])
  #  else
  #    return false
  #  end
  end
end

def getOpenList(target, openlist, closedlist)
  -1.upto(1) { |x|
    -1.upto(1) { |y|
      newx=target.x+x
      newy=target.y+y
      point=Node.new(newx, newy)
      if closedlist.index(point)==nil and $window.map.nodeMap.traversable?(newx, newy)
        if openlist.index(point)!=nil
          t=openlist[point]
          if t.hypg(target.x, target.y, target.g) < t.g
            t.parent=target.x, target.y, target.g
          end
        else
          openlist.push(Node.new(newx, newy, target.x, target.y, target.g))
        end
      end
    }
  }
  return openlist
end

def ManhattanH(tx, ty, cx, cy)
  return 10*((cx-tx).abs + (cy-ty).abs)
end

def BalancedH(tx, ty, cx, cy)
  xDistance = (cx-tx).abs
  yDistance = (cy-ty).abs
  if xDistance > yDistance
    return 14*yDistance + 10*(xDistance-yDistance)
  else
    return 14*xDistance + 10*(yDistance-xDistance)
  end
end

def getG(tx, ty, px, py, pg)
  if (tx-px)==0 or (ty-py)==0 #if there was no x change or no y change, then straight movement
    return pg+10
  else  #else diagonal movement
    return pg+14
  end
end

class AStr
  def cleanUpRoute(route, curpoint)
    newroute=Array.new
    lastpoint=curpoint
    x=0
    y=0
    route.each { |point|
      if point.x > lastpoint.x
        x=$window.map.nodeMap.nodeTopLeft(point.x,point.y)[0]
      elsif point.x < lastpoint.x
        x=$window.map.nodeMap.nodeBottomRight(point.x,point.y)[0]          
      else
        x=$window.map.nodeMap.nodeCenter(point.x,point.y)[0]
      end
      if point.y > lastpoint.y
        y=$window.map.nodeMap.nodeTopLeft(point.x,point.y)[1]
      elsif point.y < lastpoint.y
        y=$window.map.nodeMap.nodeBottomRight(point.x,point.y)[1]   
      else
        x=$window.map.nodeMap.nodeCenter(point.x,point.y)[1]
      end
      newroute.push([x,y])
      newroute.push($window.map.nodeMap.nodeCenter(point.x,point.y))
      lastpoint=point
    }
    return newroute
  end
  def *(args)
    curx, cury, destx, desty = args[0], args[1], args[2], args[3]
    curx=$window.map.nodeMap.nodeAt(curx, cury)[0]
    cury=$window.map.nodeMap.nodeAt(curx, cury)[1]
    destx=$window.map.nodeMap.nodeAt(destx, desty)[0]
    desty=$window.map.nodeMap.nodeAt(destx, desty)[1]
    curpoint=Node.new(curx, cury)
    closedlist=Array.new
    closedlist.push(curpoint)
    openlist=getOpenList(curx, curx, Array.new, closedlist)
  
    route=Array.new
  
    while true
  
    lowestf=nil
    besttarget=nil
    besttargetg=0
    parent=nil
    f=nil
    openlist.each { |target|
      f=target.g + target.h
      if f<lowestf or lowestf==nil
        lowestf=f
        besttarget=target
        besttargetg=target.g
      end
    }
  
    route.push(besttarget)
  
    if besttarget.x==destx and besttarget.y==desty
      return cleanUpRoute(route, curpoint)
    elsif openlist==Aray.new
      return false
    end
  
    openlist.delete(besttarget)
    closedlist.push(besttarget)
  
    openlist+=getOpenList(besttarget.x, besttarget.y, openlist, closedlist)
  
    end #while
    
  end
  
end

module Constants
  A=AStar::AStr.new
end

end #module AStar

end #module AI

include AI

#include AStar

include AStar::Constants
